﻿using System;
using biz.ritter.javapi.util;
using biz.ritter.javapi.lang.reflect;
using biz.ritter.javapi.lang;
using biz.ritter.javapi.lang.annotation;
using System.Runtime.CompilerServices;

namespace Nofs.Net.AnnotationDriver
{
    public class AttributeCache
    {
        private Map<Type, bool> _hasMap;
        private Map<Method, bool> _hasMapMethod;
        private Class _annotationType;
        private Class _listType;

        public AttributeCache(Class annotationType)
            : this(annotationType, null)
        {
        }

        public AttributeCache(Class annotationType, Class listType)
        {
            _hasMap = new HashMap<Type, bool>();
            _hasMapMethod = new HashMap<Method, bool>();
            _annotationType = annotationType;
            _listType = listType;
        }

        private static bool IsListOfDomainObjects(Type type, Class annotationType, Class listType)
        {
            if (type == typeof(ParameterizedType))
            {
                ParameterizedType pType = new ParameterizedType(type);
                foreach (Type typeArgument in pType.getActualTypeArguments())
                {
                    if (typeArgument == typeof(Class))
                    {
                        Class cls = typeArgument.getClass();
                        foreach (IAnnotation annotation in cls.getAnnotations())
                        {
                            if (listType != null)
                            {
                                if (annotation.annotationType().getClass() == listType)
                                {
                                    return true;
                                }
                            }
                            else if (annotation.annotationType().getClass() == annotationType)
                            {
                                return true;
                            }
                        }
                    }
                }
            }
            return false;
        }

        private static bool objHasAnnotation(AnnotatedElement ae, Class annotationType)
        {
            foreach (IAnnotation annotation in ae.getAnnotations())
            {
                if (annotation.annotationType().getClass() == annotationType)
                {
                    return true;
                }
            }
            return false;
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public bool hasAnnotation(Method method)
        {
            bool match = false;
            if (!match)
            {
                if (_hasMapMethod.containsKey(method))
                {
                    match = _hasMapMethod.get(method);
                }
                else
                {
                    match = objHasAnnotation(new AnnotatedElement(method), _annotationType);
                }
                _hasMapMethod.put(method, match);
            }
            if (!match)
            {
                match = hasAnnotation(method.getGenericReturnType());
            }
            return match;
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public bool hasAnnotation(Type cls)
        {
            bool match = false;
            if (_hasMap.containsKey(cls))
            {
                match = _hasMap.get(cls);
            }
            else
            {
                if (cls == typeof(AnnotatedElement))
                {
                    match = objHasAnnotation(new AnnotatedElement(cls), _annotationType);
                }
                if (!match && IsListOfDomainObjects(cls, _annotationType, _listType))
                {
                    match = true;
                }
                _hasMap.put(cls, match);
            }
            return match;
        }

        public bool hasAnnotation(Object obj) //throws Exception 
        {
            if (obj == null)
            {
                throw new System.Exception("obj");
            }
            return hasAnnotation(obj.getClass());
        }
    }

}
